#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import locale
import os
import threading
import time

from common.const import ExecuteResultEnum
from common.common import read_result_file
from common.common_models import ActionResult
from common.file_common import delete_file
from oracle import logger
from oracle.common.common import write_tmp_file, get_database_info, get_compatible_info, check_table_exist
from oracle.common.constants import ScriptExitCode, PluginPathConstant, ArchiveLogMode, OpenMode, ErrorCode
from oracle.common.windows_common import redirect_backup_path, execute_windows_rman_cmd, create_dir_recursive


class WindowsTableOracleRestore:
    def __init__(self, job_id, tables, sub_job_id):
        self.restore_params = {}
        self.ora_version = ''
        self.sql_params = {}
        self.rman_params = {}
        self.db_is_cluster = 0
        self.additional = ''
        self.oracle_is_cdb = ''
        self.auth_type = ''
        self.spfile_location_new = ''
        self.oracle_base = ''
        self.ora_db_group = ''
        self.task_type = 0
        self.resetlogs_id = ''
        self.rman_enc_section = ''
        self.is_enc_bk = 0
        self.pit = ''
        self.copy_restore = 0
        self.main_backup_path = ''
        self.backup_path_list = ''
        self._pid = sub_job_id
        self._sub_job_id = sub_job_id
        self._job_id = job_id
        self.db_name = ''
        self.db_instance = ''
        self.db_user = ''
        self.db_user_pwd = ''
        self.channels = 0
        self.db_type = ''
        self.enc_algo = ''
        self.enc_key = ''
        self.restore_by = ''
        self.recover_num = ''
        self.ora_db_user = ''
        self.ora_grid_user = ''
        self.backup = ''
        self.meta_data_path = ''
        self.archive = ''
        self.job_id_list = ''
        self.bct_status = ''
        self.backup_time = ''
        self.tables = tables
        self.cache_repo_path = ''

    def get_restore_vars(self, params):
        self.db_name = params['AppName']
        self.db_instance = params['InstanceName']
        self.db_user = params['UserName']
        self.db_user_pwd = params['Password']
        self.oracle_base = params['OracleBase']
        self.channels = params['Channel']
        self.db_type = params['dbType']
        self.enc_algo = params['EncAlgo']
        self.enc_key = params['EncKey']
        self.restore_by = params['RestoreBy']
        self.recover_num = params['recoverNum']
        self.ora_db_user = params['OracleInstallUser']
        self.ora_db_group = params['OracleInstallGroup']
        self.ora_grid_user = params['GridInstallUser']
        self.backup = params['DataPath']
        self.meta_data_path = params['MetaDataPath']
        self.archive = params['LogPath']
        self.job_id_list = params['JobIdList']
        self.bct_status = params['bctStatus']
        self.backup_time = params['backupTime']
        self.cache_repo_path = params['cacheRepoPath']
        self.rman_params = {
            'pid': self._pid,
            "is_enc_bk": 0,
            "instance_name": self.db_instance,
            "db_user": self.db_user,
            "db_password": self.db_user_pwd,
            "rman_enc_section": "",
            "db_install_user": self.ora_db_user,
            "db_install_group": self.ora_db_group
        }

    def windows_exe_restore(self):
        logger.info(f"Enter function windows table exe_restore, pid:{self._pid}.")
        self.define_params()
        return self.restore_table()


    def define_params(self):
        logger.info(f"table oracle restore start, pid:{self._pid}.")
        self.backup_path_list = redirect_backup_path(self.backup)
        self.main_backup_path = self.backup_path_list.split(";")[0]
        self.additional = f"{self.main_backup_path}/additional"
        backup_temp = f"{self.main_backup_path}/tmp"
        logger.info(f'BACKUPTEMP={backup_temp};ADDITIONAL={self.additional};'
                    f'MainBackupPath={self.main_backup_path}, pid:{self._pid}.')
        if self.channels:
            self.channels = int(self.channels)
        if not self.channels or self.channels == 0:
            self.channels = 4
            logger.warning(F"Setting channels number to {self.channels} by default, pid:{self._pid}.")
        if self.channels > 254:
            logger.error(f"Channels param is invalid, Channels={self.channels}, pid:{self._pid}.")
            return 1
        return ScriptExitCode.SUCCESS

    def restore_table(self):
        json_tables = eval(self.tables)
        self.delete_expire_copy()
        first_pdb_name = json_tables[0].get("pdb_name", "")
        if first_pdb_name:
            pdb_tables = {}
            # 合并表结构 pdb_table ={"pdb_name":"u1.t1, u2,t2"}
            for json_table in json_tables:
                pdb_name = json_table.get("pdb_name", "")
                if pdb_tables.get(pdb_name):
                    pdb_tables[pdb_name] = (f"{pdb_tables.get(pdb_name)}, "
                                            f"{json_table.get('user_name')}.{json_table.get('table_name')}")
                else:
                    pdb_tables[pdb_name] = f"{json_table.get('user_name')}.{json_table.get('table_name')}"
            for key, value in pdb_tables.items():
                ret_code = self.single_restore_table_windows(key, value)
                if ret_code != ScriptExitCode.SUCCESS:
                    return ret_code
            return ScriptExitCode.SUCCESS
        else:
            tables = ""
            # 合并表结构 tables ="u1.t1, u2,t2"
            for json_table in json_tables:
                if tables:
                    tables += f", {json_table.get('user_name')}.{json_table.get('table_name')}"
                else:
                    tables += f"{json_table.get('user_name')}.{json_table.get('table_name')}"
            return self.single_restore_table_windows("", tables)


    def single_restore_table_windows(self, pdb_name, tables):
        logger.info(f"Running RMAN to restore control file, pid:{self._pid}")
        restore_database_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/recover_table_oracle{self._pid}.sql"
        restore_database_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/recover_table_oracle{self._pid}.rst"
        copy_backup_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.backup_time))
        thread_id = threading.get_ident()
        tmp_file = os.path.join(self.cache_repo_path, f'table_{thread_id}')
        create_dir_recursive(tmp_file)
        dump_tmp_file = os.path.join(self.cache_repo_path, f'table_dump_{thread_id}')
        create_dir_recursive(dump_tmp_file)
        tmp_sql_content = "RUN {\n"
        tmp_sql_content += f"   configure device type disk parallelism {self.channels};\n"
        tmp_sql_content += f"   catalog start with '{self.main_backup_path}\\';\n"
        if pdb_name:
            tmp_sql_content += (f"   RECOVER TABLE {tables} OF PLUGGABLE DATABASE {pdb_name} "
                                f"UNTIL TIME \"to_date('{copy_backup_time}', 'YYYY-MM-DD HH24:MI:SS')\" "
                                f"AUXILIARY DESTINATION '{tmp_file}' "
                                f"DATAPUMP DESTINATION '{dump_tmp_file}';\n")
        else:
            tmp_sql_content += (f"   RECOVER TABLE {tables} UNTIL "
                                f"TIME \"to_date('{copy_backup_time}', 'YYYY-MM-DD HH24:MI:SS')\" "
                                f"AUXILIARY DESTINATION '{tmp_file}' "
                                f"DATAPUMP DESTINATION '{dump_tmp_file}';\n")
        tmp_sql_content += "}\n"
        write_tmp_file(tmp_sql_content, restore_database_sql)
        ret_code = execute_windows_rman_cmd(self._pid, self.rman_params, 0, restore_database_sql,
                                            restore_database_rst)
        if ret_code != ScriptExitCode.SUCCESS:
            encoding = locale.getdefaultlocale()[1]
            logger.error(
                f"Restore {self.db_name}-{self.db_instance} controlfile recover_table_oraclefailed, "
                f"ret_code={ret_code}, "
                f"error is {read_result_file(restore_database_rst, encoding=encoding)}, pid:{self._pid}")
            delete_file(restore_database_sql)
            delete_file(restore_database_rst)
            return ret_code
        delete_file(restore_database_sql)
        delete_file(restore_database_rst)
        logger.info(f"Table restore {self.db_name}-{self.db_instance} succ, pid:{self._pid}.")
        return ScriptExitCode.SUCCESS

    def delete_expire_copy(self):
        logger.info(f"start delete expire copy success, pid:{self._pid}.")
        del_expire_copy_sql = f"{PluginPathConstant.WINDOWS_TMP_PATH}/del_expire_copy{self._pid}.sql"
        del_expire_copy_rst = f"{PluginPathConstant.WINDOWS_TMP_PATH}/del_expire_copy{self._pid}.rst"
        backup_path_upper = self.main_backup_path.upper()
        tmp_sql_content = "RUN {\n"
        tmp_sql_content += f"   crosscheck copy;\n"
        tmp_sql_content += f"   delete noprompt copy like '{self.main_backup_path}\\%';\n"
        tmp_sql_content += f"   delete noprompt copy like '{backup_path_upper}\\%';\n"
        tmp_sql_content += "}\n"
        write_tmp_file(tmp_sql_content, del_expire_copy_sql)
        ret_code = execute_windows_rman_cmd(self._pid, self.rman_params, 0, del_expire_copy_sql,
                                            del_expire_copy_rst)
        if ret_code != ScriptExitCode.SUCCESS:
            encoding = locale.getdefaultlocale()[1]
            logger.error(
                f"Restore {self.db_name}-{self.db_instance} controlfile failed, ret_code={ret_code}, "
                f"error is {read_result_file(del_expire_copy_rst, encoding=encoding)}, pid:{self._pid}")
            delete_file(del_expire_copy_sql)
            delete_file(del_expire_copy_rst)
            return ret_code
        delete_file(del_expire_copy_sql)
        delete_file(del_expire_copy_rst)
        logger.info(f"delete expire copy success, pid:{self._pid}.")
        return ret_code

    def windows_table_restore_prerequisite(self):
        base_info = get_database_info(self.rman_params, self._job_id)
        if not base_info:
            logger.error(f"Get database base info failed, pid:{self._job_id}")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                bodyErr=ErrorCode.ERROR_INSTANCE_NOSTART.value)
        [log_mode, db_id, uniq_name, open_mode, incarnation_number, self.resetlogs_id] = base_info
        # 归档模式
        if log_mode != ArchiveLogMode.ARCHIVELOG.value:
            logger.error(f"table restore_prerequisite, log mode not archivelog, pid: {self._job_id}.")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                bodyErr=ErrorCode.ERROR_ORACLE_NOARCHIVE_MODE.value)
        # 读写模式
        if open_mode != OpenMode.READWRITE.value:
            logger.error(f"table restore_prerequisite, open mode not readwrite, pid: {self._job_id}.")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                bodyErr=ErrorCode.ERROR_ORACLE_NO_READWRITE_MODE.value)
        # compatible配置需要11.1.0以上 源端，目标端
        compatible = get_compatible_info(self._pid, self.db_instance, self.ora_db_user)
        compatible_versions = compatible.split(".")
        if (int(compatible_versions[0]) < 12
                or (int(compatible_versions[0]) == 11 and int(compatible_versions[1]) < 1)):
            logger.error(f"compatible_versions is lower 11.1.0")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                bodyErr=ErrorCode.ERROR_ORACLE_COMPATIBLE_VERSION_LOWER.value)
        # 恢复的表不能已存在
        json_tables = eval(self.tables)
        for table in json_tables:
            table_name = table.get("table_name")
            user_name = table.get("user_name")
            pdb_name = table.get("pdb_name")
            table_exist = check_table_exist(self._pid, self.rman_params, user_name, table_name,
                                            pdb_name)
            if table_exist:
                tables = list()
                tables.append(table_name)
                logger.error(f"table {table_name} exits")
                return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR.value,
                                    bodyErr=ErrorCode.ERROR_EXIST_SAME_TABLE_NAME.value, bodyErrParams=tables)
        return ActionResult(code=ExecuteResultEnum.SUCCESS.value)